package com.hup.utils.swing;

import com.hup.utils.commons.start.LookThemeUtil;
import lombok.extern.slf4j.Slf4j;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

/**
 * @author hugan
 * @date 2020/4/15
 */
@Slf4j
public class ImageUtil {


    public static BufferedImage subSafely(BufferedImage src, Rectangle rect) {
        return subSafely(src, rect.x, rect.y, rect.width, rect.height);
    }

    /**
     * 安全地从src中截取片段,允许片段部分越界(调用者仍要做下述的必要校验),越界的范围会变成透明像素;<br>
     * 可以使用 {@link #fillMosaic(Graphics2D, int, int, int, int)}对透明背景添加马赛克<br>
     * (因为目标片段很小时[小于12像素],会使 fillMosaic 方法没有绘制内容,所以本方法不会在返回前添加马赛克背景)
     *
     * @param src notNull,有长度的
     * @param x 目标片段的起始坐标,允许越界(小于0或大于原宽度)
     * @param w 目标片段的尺寸,大于0,允许越界(大于原宽度)
     * @return notNull
     * @throws IllegalArgumentException 原图或目标无尺寸;目标区域全部都在图像外
     * @apiNote 处理如[截屏时, 放大镜图像在屏幕边缘越界]的问题
     */
    @SuppressWarnings("Duplicates")
    public static BufferedImage subSafely(BufferedImage src, int x, int y, int w, int h) {
        int srcW = src.getWidth();
        int srcH = src.getHeight();
        log.debug("x={}, y={}, w={}, h={}", x, y, w, h);
        log.debug("srcW={}, srcH={}", srcW, srcH);
        if (srcW <= 0 || srcH <= 0) throw new IllegalArgumentException("原图尺寸异常");
        if (w <= 0 || h <= 0) throw new IllegalArgumentException("目标尺寸异常");
        if (x + w < 0 || y + h < 0 || x > srcW || y > srcH)
            throw new IllegalArgumentException("目标区域全部都在图像外");

        boolean notSafe = x < 0 || y < 0 || x + w > srcW || y + h > srcH;
        if (!notSafe) return src.getSubimage(x, y, w, h);

        log.debug("notSafe");
        //修正后的截取尺寸
        int subX = x;
        int subY = y;
        int subW = w;
        int subH = h;
        //截图图像在res中的绘制起点
        int drawX = 0, drawY = 0;
        if (x < 0) {
            subX = 0;
            subW = w + x;
            drawX = -x;
        } else if (x + w > srcW) {
            subW = srcW - x;
        }
        if (y < 0) {
            subY = 0;
            subH = h + y;
            drawY = -y;
        } else if (y + h > srcH) {
            subH = srcH - y;
        }
        Graphics2D g = null;
        try {
            log.debug("subX={}, subY={}, subW={}, subH={}", subX, subY, subW, subH);
            BufferedImage res = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
            g = res.createGraphics();
            BufferedImage sub = src.getSubimage(subX, subY, subW, subH);
            g.drawImage(sub, drawX, drawY, subW, subH, null);
            return res;
        } finally {
            //按上面的逻辑,应该是不会发生jdk的越界异常的
            if (g != null) g.dispose();
        }
    }

    /**
     * 在指定位置,绘制马赛克背景(白底,灰色格子)
     */
    public static void fillMosaic(Graphics2D g, Rectangle rect) {
        fillMosaic(g, rect.x, rect.y, rect.width, rect.height);
    }

    /**
     * 在指定位置,绘制马赛克背景(白底,灰色格子)
     *
     * @param x 起始坐标
     * @param w 当前画布大小
     */
    public static void fillMosaic(Graphics2D g, int x, int y, int w, int h) {
        /*
        用带宽度的线画n行,实现马赛克效果
        要处理的问题:
        .由于带宽度的横线,会导致Y轴上下端超出绘制区域,所以要二次处理
        .X周方向是不会越界的
         */
        int lineW = (int) (12 * LookThemeUtil.getScreenScale());//格子宽度,也是线宽
        int row = h / lineW;//画n行,完整的线
        log.debug("h={}, 线宽={}, row={}", h, lineW, row);
        //白底
        g.setColor(Color.WHITE);
        g.fillRect(x, y, w, h);

        //马赛克线
        g.setColor(new Color(222, 222, 222));
        g.setStroke(new BasicStroke(lineW, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[]{lineW, lineW}, 0));

        //画n条完整的横线
        boolean xMoveBack = false;//当前行是否右移起点
        int lineY0 = y + lineW / 2;//要下移半个线宽,否则马赛克顶部会小于y;这里是上衣
        for (int i = 0; i < row; i++) {
            int lineY = lineY0 + i * lineW;
            g.drawLine(x + (xMoveBack ? 0 : lineW), lineY, x + w - 1, lineY);
            xMoveBack = !xMoveBack;
        }

        //最后一行肯定是没画到,且高度 小于等于线宽:用宽度是1像素的线逐行画
        g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[]{lineW, lineW}, 0));
        lineY0 = y + row * lineW;
        for (int i = 0; i < lineW; i++) {
            int lineY = lineY0 + i;
            if (lineY >= y + h) return;//当前y坐标越界时就可以return了
            g.drawLine(x + (xMoveBack ? 0 : lineW), lineY, x + w - 1, lineY);
        }
    }

    public static BufferedImage getImage(Image image) {
        if (image == null) throw new IllegalArgumentException("图像为空");
        if (image instanceof BufferedImage) return (BufferedImage) image;

        BufferedImage bf = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = bf.createGraphics();
        try {
            g.drawImage(image, 0, 0, null);
        } finally {
            g.dispose();
        }
        return bf;
    }

    /**
     * 清空画布指定区域;
     * 原理:使用setComposite,将透明色填充指定区域
     */
    public static void clearCanvas(Graphics2D g, int x, int y, int width, int height) {
        Composite defComposite = g.getComposite();
        g.setComposite(AlphaComposite.Src);
        g.setColor(new Color(0, 0, 0, 0));
        g.fillRect(x, y, width, height);
        //必须还原!
        g.setComposite(defComposite);
    }

    /**
     * 获取image的副本图像,副本图像是:使用指定颜色填充非空像素点
     */
    public static BufferedImage getFillBackgroundImage(BufferedImage image, Color color) {
        int colorInt = color.getRGB();
        int width = image.getWidth();
        int height = image.getHeight();
        int[] rgb = image.getRGB(0, 0, width, height, null, 0, width);
        int size = width * height;
        for (int i = 0; i < size; i++) {
            if (rgb[i] != 0) rgb[i] = colorInt;
        }
        BufferedImage res = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        res.setRGB(0, 0, width, height, rgb, 0, width);
        return res;
    }

    /**
     * 设置图像"非透明像素"的透明度
     *
     * @param image 要修改的图像
     * @param alpha [0,255],越大越不透明
     */
    public static BufferedImage updateAlpha(BufferedImage image, int alpha) {
        int width = image.getWidth();
        int height = image.getHeight();
        int[] rgb = image.getRGB(0, 0, width, height, null, 0, width);
        int size = width * height;
        for (int i = 0; i < size; i++) {
            int argb = rgb[i];
            if (argb == 0) continue;
            int r = (argb >> 16) & 0xff;
            int g = (argb >> 8) & 0xff;
            int b = (argb) & 0xff;
            rgb[i] = ColorUtil.getColor(alpha, r, g, b);
        }
        image.setRGB(0, 0, width, height, rgb, 0, width);
        return image;
    }

    /**
     * 按比例缩放图像,
     * 场景如: 缩略图
     *
     * @param maxWidth 最大目标尺寸
     * @param maxHeight 最大目标尺寸
     */
    public static BufferedImage getScaleImage(BufferedImage src, int maxWidth, int maxHeight) {
        int w0 = src.getWidth();
        int h0 = src.getHeight();
        float ratio0 = (w0 + 0F) / h0;
        //log.info("src=[{}, {}], ratio0={}", w0, h0, ratio0);

        float ratioRes = (maxWidth + 0F) / maxHeight;
        int width = maxWidth;
        int height = maxHeight;
        if (ratioRes < ratio0) {
            height = (int) (maxWidth / ratio0);
        } else {
            width = (int) (maxHeight * ratio0);
        }
        //log.info("res=[{}, {}]", width, height);

        Image scaled = src.getScaledInstance(width, height, Image.SCALE_DEFAULT);
        return getImage(scaled);
    }

}
